Building Custom Attributes

The first step in building a custom attribute is to create a new class deriving from System.Attribute. Keeping in step with the automobile theme used throughout this book, assume you have created a brand new C# Class Library project named AttributedCarLibrary. This assembly will define a handful of vehicles, each of which is described using a custom attribute named VehicleDescriptionAttribute:

// A custom attribute.
public sealed class VehicleDescriptionAttribute : System.Attribute
{
    public string Description { get; set; }

    public VehicleDescriptionAttribute(string vehicalDescription)
    {
        Description = vehicalDescription;
    }
    public VehicleDescriptionAttribute(){ }
}

As you can see, VehicleDescriptionAttribute maintains a piece of string data manipulated using an automatic property (Description). Beyond the fact that this class derived from System.Attribute, there is nothing unique to this class definition.

Note For security reasons, it is considered a .NET best practice to design all custom attributes as sealed. In fact, Visual Studio 2010 provides a code snippet named Attribute that will dump out a new System.Attribute-derived class into your code window. See Chapter 2 for an explication of using code snippets.

Applying Custom Attributes

Given that VehicleDescriptionAttribute is derived from System.Attribute, you are now able to annotate your vehicles as you see fit. For testing purposes, add the following class definitions to your new class library:

// Assign description using a "named property."
[Serializable]
[VehicleDescription(Description = "My rocking Harley")]
public class Motorcycle
{
}

[SerializableAttribute]
[ObsoleteAttribute("Use another vehicle!")]
[VehicleDescription("The old gray mare, she ain't what she used to be...")]
public class HorseAndBuggy
{
}

[VehicleDescription("A very long, slow, but feature-rich auto")]
public class Winnebago
{
}

Named Property Syntax

Notice that the description of the Motorcycle is assigned a description using a new bit of attributecentric syntax termed a named property. In the constructor of the first [VehicleDescription] attribute, you set the underlying string data by using the Description property. If this attribute is reflected upon by an external agent, the value is fed into the Description property (named property syntax is legal only if the attribute supplies a writable .NET property).

In contrast, the HorseAndBuggy and Winnebago types are not making use of named property syntax and are simply passing the string data via the custom constructor. In any case, once you compile the AttributedCarLibrary assembly, you can make use of ildasm.exe to view the injected metadata descriptions for your type. For example, Figure 15-5 shows an embedded description of the Winnebago class, specifically the data within the beforefieldinit item in ildasm.exe.

Figure 15-5

Figure 15-5 Embedded vehicle description data

Restricting Attribute Usage

By default, custom attributes can be applied to just about any aspect of your code (methods, classes, properties, and so on). Thus, if it made sense to do so, you could use VehicleDescription to qualify methods, properties, or fields (among other things):

[VehicleDescription("A very long, slow, but feature-rich auto")]
public class Winnebago
{
    [VehicleDescription("My rocking CD player")]
    public void PlayMusic(bool On)
    {
        ...
    }
}

In some cases, this is exactly the behavior you require. Other times, however, you may want to build a custom attribute that can be applied only to select code elements. If you wish to constrain the scope of a custom attribute, you will need to apply the [AttributeUsage] attribute on the definition of your custom attribute. The [AttributeUsage] attribute allows you to supply any -combination of values (via an OR operation) from the AttributeTargets enumeration:

// This enumeration defines the possible targets of an attribute.
public enum AttributeTargets
{
    All, Assembly, Class, Constructor,
    Delegate, Enum, Event, Field, GenericParameter,
    Interface, Method, Module, Parameter,
    Property, ReturnValue, Struct
}

Furthermore, [AttributeUsage] also allows you to optionally set a named property (AllowMultiple) that specifies whether the attribute can be applied more than once on the same item (the default is false). As well, [AttributeUsage] allows you to establish whether the attribute should be inherited by derived classes using the Inherited named property (the default is true).

To establish that the [VehicleDescription] attribute can be applied only once on a class or structure, you can update the VehicleDescriptionAttribute definition as follows:

// This time, we are using the AttributeUsage attribute
// to annotate our custom attribute.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct,
    Inherited = false)]
public sealed class VehicleDescriptionAttribute : System.Attribute
{
...
}

With this, if a developer attempted to apply the [VehicleDescription] attribute on anything other than a class or structure, he or she is issued a compile-time error.